// 标签模块，为数据加上标签
#pragma once

#include <string>
#include <vector>
#include <unordered_map>
#include <iostream>
#include <cstdint>
#include <mutex>
#include <thread>
#include "util.hpp"
#include "log.hpp"

extern Log lg;

namespace ns_index
{
    using weight_t = int;
    struct doc_info
    {
        uint64_t doc_id;
        std::string title;
        std::string content;
        std::string url;
    };

    struct inverted_elem
    {
        uint64_t doc_id;
        std::string key_word;
        weight_t weight = 0; // 权重
    };

    class index
    {
    public:
        using docInfo_t = doc_info;
        using invertedElem_t = inverted_elem;
        using inverted_list = std::vector<invertedElem_t>;

    public:
        const docInfo_t* get_forward_index(const uint64_t id) // 正排索引
        {
            if (id >= forward_index.size())
            {
                // std::cerr << "this id is too big!" << std::endl;
                lg(Warning, "this forward_index id is too big: %d, forward_index size: %d", id, forward_index.size());
                return nullptr;
            }
            return &forward_index[id];
        }

        const inverted_list* get_inverted_index(const std::string& key_word)
        {
            auto iter = inverted_index.find(key_word);
            if (iter == inverted_index.end())
            {
                // std::cerr << "can't find this key word!\n";
                lg(Info, "inverted_index not have this word: %s", key_word.c_str());
                return nullptr;
            }
            return &iter->second;
        }

        bool create_index() // 根据文档内容建立索引
        {
            static std::thread forward_build(&index::build_forward_index, this);
            static std::thread inverted_build(&index::build_inverted_index, this);
            forward_build.join();
            inverted_build.join();
            return true;
        }

        static index* get_instance()
        {
            if (nullptr == ins) // 提高效率
            {
                // 多线程防止两次创建，需要加锁
                mtx.lock();
                if (nullptr == ins)
                    ins = new index();
                mtx.unlock();
            }
            return ins;
        }

    public:
        static index* ins;
        static std::mutex mtx;

    private: // 将index模块修改成单例模式
        index() {};
        ~index() = delete;
        index(const index&) = delete;
        index& operator=(const index&) = delete;

    private:
        std::vector<docInfo_t> forward_index; // 正排索引
        std::unordered_map<std::string, inverted_list> inverted_index;
        const std::string sep = "\4";
        const int title_weight = 100;
        const int content_weight = 1;

    public:
        bool build_forward_index()
        {
            mysqlpp::Connection con(false);
            con.connect(database.c_str(), host_name.c_str(), user_name.c_str(), passwd.c_str(), port);
            if (!con.connected())
            {
                lg(Fatal, "mysql connect error in forward_index, err: %s", con.error());
                return false;
            }
            mysqlpp::Query q = con.query("select * from forward_index");
            auto res = q.store();
            if (!res)
            {
                lg(Fatal, "read mysql error, %s", q.error());
                return false;
            }
            for (size_t i = 0; i < res.num_rows(); ++i)
            {
                docInfo_t doc;
                auto& r = res[i];
                doc.doc_id = i + 1;
                doc.title = r["title"].c_str();
                doc.content = r["content"].c_str();
                doc.url = r["url"].c_str();
                forward_index.push_back(std::move(doc));
            }
            lg(Info, "forward_index create sucess!");
            return true;
        }

        bool build_inverted_index()
        {
            mysqlpp::Connection con(false);
            con.connect(database.c_str(), host_name.c_str(), user_name.c_str(), passwd.c_str(), port);

            if (!con.connected())
            {
                lg(Fatal, "mysql connect error in inverted_index, err: %s", con.error());
                return false;
            }
            mysqlpp::Query q = con.query("select * from inverted_index");
            auto res = q.store();

            if (!res)
            {
                lg(Fatal, "read mysql error, %s", q.error());
                return false;
            }

            for (size_t i = 0; i < res.num_rows(); ++i)
            {
                auto& row = res[i];
                std::string word = row["word"].c_str();
                std::string json = row["file_list"].c_str();
                Json::Value v;
                inverted_list list;
                if (!ns_util::json_util::parse_json(json, &v))
                {
                    lg(Warning, "parse json from %s error", word.c_str());
                    continue;
                }
                // 从数据库中读取item插入
                for (const auto& item : v)
                {
                    invertedElem_t elem;
                    elem.key_word = word;
                    elem.doc_id = item["doc_id"].asInt();
                    elem.weight = item["weight"].asInt();
                    list.push_back(std::move(elem));
                }
                inverted_index[word] = std::move(list);
            }
            lg(Info, "create inverted_index success!");
            return true;
        }
    };
    std::mutex index::mtx;
    index* index::ins = nullptr;
}
